﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation.Collections;

namespace StyleMVVM.Data.Collections
{
	public class WeakDictionary<K, V> : IObservableMap<K, V> where V : class
	{
		private readonly ObservableDictionary<K, WeakReference> internalCollection;

		public WeakDictionary()
		{
			internalCollection = new ObservableDictionary<K, WeakReference>();

			internalCollection.MapChanged += internalCollectionMapChanged;
		}

		public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
		{
			return new WeakDictionaryEnumerator<K, V>(this);
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		public void Add(KeyValuePair<K, V> item)
		{
			internalCollection.Add(
				new KeyValuePair<K, WeakReference>(item.Key, new WeakReference(item.Value)));
		}

		public void Clear()
		{
			internalCollection.Clear();
		}

		public bool Contains(KeyValuePair<K, V> item)
		{
			return internalCollection.ContainsKey(item.Key);
		}

		public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
		{
			if ((array.Length - arrayIndex) >= internalCollection.Count)
			{
				throw new ArgumentException("Array to small");
			}

			int i = 0;

			List<KeyValuePair<K, WeakReference>> copyList =
				new List<KeyValuePair<K, WeakReference>>(internalCollection);

			foreach (KeyValuePair<K, WeakReference> valuePair in copyList)
			{
				V target = valuePair.Value.Target as V;

				if (target != null)
				{
					array[i + arrayIndex] = new KeyValuePair<K, V>(valuePair.Key, target);
					i++;
				}
				else
				{
					internalCollection.Remove(valuePair.Key);
				}
			}
		}

		public bool Remove(KeyValuePair<K, V> item)
		{
			return internalCollection.Remove(item.Key);
		}

		public int Count
		{
			get { return internalCollection.Count; }
		}

		public bool IsReadOnly
		{
			get { return false; }
		}

		public void Add(K key, V value)
		{
			internalCollection.Add(key, new WeakReference(value));
		}

		public bool ContainsKey(K key)
		{
			return internalCollection.ContainsKey(key);
		}

		public bool Remove(K key)
		{
			return internalCollection.Remove(key);
		}

		public bool TryGetValue(K key, out V value)
		{
			WeakReference reference;

			if (internalCollection.TryGetValue(key, out reference))
			{
				value = reference.Target as V;

				if (value != null)
				{
					return true;
				}

				internalCollection.Remove(key);
			}

			value = null;

			return false;
		}

		public V this[K key]
		{
			get
			{
				V region = null;
				WeakReference reference;

				if (internalCollection.TryGetValue(key, out reference))
				{
					region = reference.Target as V;

					if (region == null)
					{
						internalCollection.Remove(key);
					}
				}

				return region;
			}
			set { internalCollection[key] = new WeakReference(value); }
		}

		public ICollection<K> Keys
		{
			get { return internalCollection.Keys; }
		}

		public ICollection<V> Values
		{
			get
			{
				ICollection<V> returnValue = new Collection<V>();
				List<K> keys = new List<K>(internalCollection.Keys);

				foreach (K key in keys)
				{
					V region = this[key];

					if (region != null)
					{
						returnValue.Add(region);
					}
					else
					{
						internalCollection.Remove(key);
					}
				}

				return returnValue;
			}
		}

		public event MapChangedEventHandler<K, V> MapChanged;

		public bool TryAdd(K key, V region)
		{
			if (!internalCollection.ContainsKey(key))
			{
				internalCollection[key] = new WeakReference(region);

				return true;
			}

			return false;
		}

		private void internalCollectionMapChanged(IObservableMap<K, WeakReference> sender, IMapChangedEventArgs<K> @event)
		{
			if (MapChanged != null)
			{
				MapChanged(this, @event);
			}
		}
	}

	public class WeakDictionaryEnumerator<K, V> : IEnumerator<KeyValuePair<K, V>>
	{
		private IEnumerator<K> enumerator;
		private IObservableMap<K, V> regionCollection;

		public WeakDictionaryEnumerator(IObservableMap<K, V> regionCollection)
		{
			this.regionCollection = regionCollection;
			enumerator = regionCollection.Keys.GetEnumerator();
		}

		public bool MoveNext()
		{
			return enumerator.MoveNext();
		}

		public void Reset()
		{
			enumerator.Reset();
		}

		public KeyValuePair<K, V> Current
		{
			get
			{
				return
					new KeyValuePair<K, V>(
						enumerator.Current, regionCollection[enumerator.Current]);
			}
		}

		object IEnumerator.Current
		{
			get { return Current; }
		}

		public void Dispose()
		{
			enumerator = null;
			regionCollection = null;
		}
	}
}